<template>
  <el-dialog v-model="show" width="800" :title="title" destroy-on-close>
    <template #default>
      <el-form ref="formRef" label-width="80" :model="model" :rules="rules">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="名称" prop="name">
              <el-input v-model="model.name" placeholder="请填写名称" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="标识" prop="label">
              <el-input v-model="model.label" placeholder="请填写标识" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="备注">
          <el-input v-model="model.remark" type="textarea" placeholder="请填写备注" />
        </el-form-item>
        <el-form-item label="功能权限">
          <el-input v-model="keyword" placeholder="请输入关键字进行过滤" />
          <el-tree
            ref="treeRef"
            :data="tree"
            node-key="id"
            :props="{ label: 'name' }"
            :filter-node-method="filterNodeMethod"
            show-checkbox
            default-expand-all
          />

          <!-- <el-tree
            :default-checked-keys="[2]"
            node-key="id"
            :data="[{ id: 1, hello: '张三', children: [{ id: 2, hello: '张三-1' }] }]"
            :props="{ label: 'hello' }"
            show-checkbox
          /> -->
        </el-form-item>
      </el-form>
    </template>
    <template #footer>
      <el-button type="default" @click="handleCancel">取消</el-button>
      <el-button type="primary" @click="handleOk">确认</el-button>
    </template>
  </el-dialog>
</template>

<script lang="ts" setup>
import { nextTick, onMounted, reactive, ref, watch } from 'vue'
import { getMenuList } from '@/api/menu'
import { listToTree, getChildrenIds, isLeafId } from '@/utils'
import { ElTree, type FormInstance } from 'element-plus'
import { addRole, updateRole } from '@/api/role'
import { isLeaf } from 'element-plus/es/utils'

// 定义组件的自定义触发事件
const emit = defineEmits(['success'])

const show = ref(false)
const title = ref('新增')
// 用于模板引用 Tree 组件的实例
const treeRef = ref<InstanceType<typeof ElTree> | null>(null)
// 过滤 Tree 数据的一个关键字
const keyword = ref('')
// menuList 数据是一个扁平的数据
const list = ref<IMenu[]>([])
// Tree 的数据
const tree = ref<IMenu[]>([])
// 表单绑定的数据
const model = reactive({
  id: null as null | undefined,
  name: '',
  label: '',
  remark: '',
  menuIdList: [] as number[],
  relevance: 0,
  departmentIdList: []
})
// 表单校验的规则
const rules = {
  name: [{ required: true, message: '请填写' }],
  label: [{ required: true, message: '请填写' }]
}
const formRef = ref<FormInstance | null>(null)

// 监听 keyword 的变化，去调用 Tree 组件实例的 filter 过滤方法，让 Tree 组件过滤
watch(keyword, (newVal) => {
  console.log('keyword改变了', newVal)
  treeRef.value?.filter(newVal)
})

// Tree 组件过滤时，会用到的过滤回调函数
/**
 * 1. 当 keyword 数据改变时，watch 执行，内部调用了 Tree 组件实例的 filter 函数，且将 newVal 传递
 * 2. 当 filter 函数被执行的时候，Tree 组件内部，会做循环调用传递给它的 filterNodeMethod, 该方法的
 *    第一个参数，是传递给 filter() 的参数
 *    第二隔参数，是当前要处理的节点，就类似于 数组的 filter() 的回调函数中的 item 一样
 * 3. filterNodeMethod 方法，需要控制返回 true 或 false
 *    true  - 当前节点就会显示
 *    false - 当前节点就会隐藏
 */
function filterNodeMethod(value: string, data: any) {
  console.log('filterNodeMethod', value, data)
  // return false
  // 根据 data.name 与 value 是否匹配，来控制当前节点是否显示
  return data.name.includes(value)
}

// 获取菜单列表数据
async function fetchMenuList() {
  // 请求接口
  const { data } = await getMenuList()
  // 将该数据直接赋值给 list
  list.value = data
  // 将接口返回的平铺数据转成tree数据
  tree.value = listToTree(data, null)
}

// 取消按钮的点击
function handleCancel() {
  // show.value = false
}

// 确认按钮的点击
async function handleOk() {
  // 1. 表单校验
  await formRef.value?.validate()
  // 2. 获取当前 Tree 组件上勾选的数据是哪些。[462]
  //    如果父组件是半选择的状态，也需要，所以需要将
  //    getCheckedKeys() 与 getHalfCheckedKeys() 返回的数组拼接好之后再作为参数传递给后端
  const checkedKeys = treeRef.value?.getCheckedKeys() || []
  const halfCheckedKeys = treeRef.value?.getHalfCheckedKeys() || []
  const menuIdList = [...checkedKeys, ...halfCheckedKeys]
  // 3. 判断当前的操作类型，是新增还是编辑
  console.log(model.id ? '编辑' : '新增')
  if (model.id) {
    // 编辑
    await updateRole({
      ...model,
      menuIdList
    })
  } else {
    // 新增
    await addRole({
      ...model,
      menuIdList
    })
  }
  // 4. 操作成功，通知父组件更新表格的数据
  emit('success')
  // 5. 主动关闭弹窗
  show.value = false
}

// open 方法
function open(role?: any) {
  // 打开弹窗
  show.value = true

  if (role) {
    console.log(role)
    // 编辑
    title.value = '编辑'
    // 数据填充：将 role 中的数据写入到 model
    model.name = role.name
    model.label = role.label
    model.remark = role.remark
    model.menuIdList = role.menuIdList
    model.id = role.id

    // nextTick(() => {
    //   console.log(treeRef.value, '===')
    //   // 1. 编辑后端给 backId 中的每个 id,
    //   // 2. 判断当前遍历的 id 它有几个 子节点，获取到它的子节点的 id 集合
    //   // 3. 判断它的子节点id集合是否都存在 backId 中。如果在当前的Id保留，否则当前Id要去掉
    //   const checkedKeys = role.menuIdList.filter((id: number) => {
    //     // 根据当前 id 获取它的 子节点Id 的集合
    //     const childrenIds = getChildrenIds(list.value, id, [])
    //     // 判断 childrenIds 中每一项是否都在 backId 中
    //     return childrenIds.every((cId) => role.menuIdList.includes(cId))
    //   })
    //   console.log('后端给的：', role.menuIdList)
    //   console.log('我要勾的：', checkedKeys)
    //   treeRef.value?.setCheckedKeys(checkedKeys)
    // })

    // 上面的一大段逻辑，可以简化成，将后端的返回的 menuIdList 做过滤，过滤出属于 叶子节点的 Id。用作 setCheckedKeys 的参数即可。
    // 没有子节点的节点就叫做叶子节点
    nextTick(() => {
      // 1.
      // const checkedKeys = role.menuIdList.filter((id: number) => isLeafId(list.value, id))
      // 2.
      const checkedKeys = role.menuIdList.filter((id: number) => treeRef.value?.getNode(id).isLeaf)

      console.log('需要勾选的:', checkedKeys)
      treeRef.value?.setCheckedKeys(checkedKeys)
    })
  } else {
    // 新增
    title.value = '新增'
    // 数据重置：将 model 的数据全部处理成默认值
    model.name = ''
    model.label = ''
    model.remark = ''
    model.menuIdList = []
    model.id = null
  }
}

onMounted(() => {
  fetchMenuList()
})

// 对父组件的暴露
defineExpose({
  open
})
</script>

<style lang="scss" scoped>
.el-tree {
  margin-top: 10px;
  width: 100%;
  padding: 16px 10px;
  border: 1px solid var(--el-border-color);
  border-radius: var(--el-border-radius-base);
}
</style>
